package com.ask.service.Impl;

import com.ask.entity.Dish;
import com.ask.entity.Rating;
import com.ask.mapper.DishMapper;
import com.ask.mapper.OrderMapper;
import com.ask.mapper.RatingMapper;
import com.ask.result.PageResult;
import com.ask.result.Result;
import com.ask.service.DishService;
import com.ask.service.RecommendService;
import com.ask.utils.SimilarityUtils;
import com.ask.vo.DishVO;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;

import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import static com.ask.constant.NumberConstant.DAILY_RECOMMEND_COUNT;
import static com.ask.constant.NumberConstant.SEVEN_DAYS;
import static com.ask.constant.RedisConstant.RECOMMEND_DAILY_KEY;

@Service
public class RecommendServiceImpl implements RecommendService {
    @Autowired
    private RedisTemplate redisTemplate;
    @Autowired
    private DishService dishService;
    @Autowired
    private OrderMapper orderMapper;
    @Autowired
    private DishMapper dishMapper;
    @Autowired
    private RatingMapper ratingMapper;

    /**
     * 生成每日推荐
     *
     * @param userId
     * @return
     */
    @Override
    public List<Dish> generateDailyRecommendations(Long userId) {
        List<Dish> recommendations = getOrderBasedRecommendations(userId);
        return recommendations;
    }

    private List<Dish> getOrderBasedRecommendations(Long userId) {
        // 1. 获取用户购买过的菜品id
        List<Long> purchasedIds = orderMapper.listDishIdsByUserId(userId);

        // todo 获取用户收藏过的菜品种类

        // 冷启动：如果用户没有购买过菜品，则返回热门菜品
        if (purchasedIds.isEmpty())
            return dishMapper.getHotDishes(DAILY_RECOMMEND_COUNT, Collections.emptySet());


        // 2. 提取用户购买过的菜品种类
        Set<Long> categoryIds = dishMapper.getByIds(purchasedIds).stream()
                .map(Dish::getCategoryId)
                .collect(Collectors.toSet());

        // 从redis中获取近7日的推荐菜品id
        // todo 如何高效获取近7日redis数据
        Set<Long> excludeIds = new HashSet<>(purchasedIds); // 排除列表：1. 已购买过的 2.近7日的推荐过的
        LocalDate today = LocalDate.now();
        for (int i = 1; i <= SEVEN_DAYS; i++) {
            String key = RECOMMEND_DAILY_KEY + userId + ":" + today.minusDays(i).format(DateTimeFormatter.BASIC_ISO_DATE);
            if (redisTemplate.hasKey(key)) {
                Set<Long> ids = redisTemplate.opsForZSet().range(key, 0, DAILY_RECOMMEND_COUNT - 1);
                if (ids != null && !ids.isEmpty())
                    excludeIds.addAll(ids);
            }
        }


        // 3. 基于菜品种类生成基础推荐, 过滤掉用户已经购买的菜品
        List<Dish> recommendedDishes = dishMapper.listByCategoryIdsAndExcludeDishIds(categoryIds, excludeIds);

        // 4. 兜底策略：当推荐不足时，加入热门商品
        if (recommendedDishes.size() < DAILY_RECOMMEND_COUNT) {
            // 构建兜底策略的排除ids
            Set<Long> excludeIds4Hot = Stream.concat(
                    excludeIds.stream(), // 排除购买过的和近7日的推荐过的菜品id
                    recommendedDishes.stream().map(Dish::getId) // 排除基础推荐中的菜品id
            ).collect(Collectors.toSet());
            recommendedDishes.addAll(dishMapper.getHotDishes(DAILY_RECOMMEND_COUNT - recommendedDishes.size(), excludeIds4Hot));
        }

        return recommendedDishes.stream()
                .sorted(Comparator.comparing(Dish::getSales).reversed())
                .limit(DAILY_RECOMMEND_COUNT)
                .collect(Collectors.toList());
    }


    @Override
    public List<Long> getSimilarItems(Long userId) {

        // 1. 获取用户评分过的商品id
        List<Long> ratedItemIds = ratingMapper.findByUserId(userId).stream()
                .map(Rating::getItemId)
                .collect(Collectors.toList());

        // itemUserRatings 是商品-用户评分矩阵
        Map<Long, Map<Long, Double>> itemUserRatings = getItemUserRatings();

        // 存储物品与相似度
        Map<Long, Double> similarityScores = new HashMap<>();

        // 遍历用户评分过的商品id
        for (Long ratedItemId : ratedItemIds) {
            // 获取当前评分过的商品的用户评分向量
            Map<Long, Double> ratedItemRatings = itemUserRatings.get(ratedItemId);

            // 遍历其他商品
            for (Long otherItemId : itemUserRatings.keySet()) {
                if (!ratedItemIds.contains(otherItemId)) { // 如果当前商品没有被评分过，则计算相似度
                    Map<Long, Double> otherItemRatings = itemUserRatings.get(otherItemId);
                    // 计算商品间的相似度
                    double similarity = SimilarityUtils.calculateCosineSimilarity(ratedItemRatings, otherItemRatings);
                    // 累加相似度
                    similarityScores.merge(otherItemId, similarity, Double::sum);

                }
            }
        }

        // 根据相似度排序并返回前 numItems 个商品
        return similarityScores.entrySet().stream()
                .sorted((e1, e2) -> Double.compare(e2.getValue(), e1.getValue())) // 按相似度降序排序
                .limit(10) // 取前 numItems 个
                .map(Map.Entry::getKey) // 只返回商品 ID
                .collect(Collectors.toList());
    }

    /**
     * 构建用户-物品评分矩阵
     *
     * @return
     */
    private Map<Long, Map<Long, Double>> getItemUserRatings() {
        // 从数据库加载 Rating 表数据
        List<Rating> ratings = ratingMapper.findAll();

        Map<Long, Map<Long, Double>> itemUserRatings = new HashMap<>();
        for (Rating rating : ratings) {
            itemUserRatings
                    .computeIfAbsent(rating.getItemId(), k -> new HashMap<>())
                    .put(rating.getUserId(), rating.getScore());
        }
        return itemUserRatings;
    }

    /**
     * 分页查询每日推荐
     *
     * @param userId
     * @param page
     * @param size
     * @return
     */
    @Override
    public PageResult getDailyRecommendations(Long userId, int page, int size) {
        // 1. 生成当日redis key
        String key = RECOMMEND_DAILY_KEY + userId + ":" + LocalDate.now().format(DateTimeFormatter.BASIC_ISO_DATE);

        // 2. 分页查询redis
        long start = (page - 1) * size;
        long end = start + size - 1;

        Set<String> dishIds = redisTemplate.opsForZSet().reverseRange(key, start, end);

        if (dishIds == null || dishIds.isEmpty()) {
            return new PageResult(0, null);
        }

        // 3. 根据菜品id查询菜品信息
        List<DishVO> dishes = dishIds.stream().map(dishId ->
                dishService.getByIdWithFlavor(Long.valueOf(dishId))
        ).collect(Collectors.toList());


        return new PageResult(dishes.size(), dishes);
    }

    /**
     * 基于物品的协调过滤推荐
     *
     * @param itemId
     * @param topN
     * @return
     */
    @Override
    public Result recommendItems(Long itemId, int topN) {
        List<Rating> ratings = ratingMapper.findAll();

        // 构建用户-物品评分矩阵
        Map<Long, Map<Long, Double>> itemUserRatings = new HashMap<>();
        for (Rating rating : ratings) {
            itemUserRatings
                    .computeIfAbsent(rating.getItemId(), k -> new HashMap<>())
                    .put(rating.getUserId(), rating.getScore());
        }

        // 获取目标商品的评分
        Map<Long, Double> targetItemRatings = itemUserRatings.get(itemId);

        // 计算其他商品与目标商品的相似度
        Map<Long, Double> similarityScores = new HashMap<>();
        for (Long otherItemId : itemUserRatings.keySet()) {
            if (!otherItemId.equals(itemId)) {
                double similarity = SimilarityUtils.calculateCosineSimilarity(
                        targetItemRatings, itemUserRatings.get(otherItemId));
                similarityScores.put(otherItemId, similarity);
            }
        }

        return Result.success(similarityScores);

    }
}
